package com.thot.timyag.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.thot.timyag.core.support.SelfClassPathScanner;
import com.thot.timyag.core.support.SelfMapperFactoryBean;
import com.thot.timyag.core.support.SelfPathMatchingResolver;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * @author yangjf
 * @version 0.1.0
 * @time 2018/10/9/009
 */
@Component
@Slf4j
public class ApplicationContextProvider
        implements ApplicationContextAware {
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextProvider.applicationContext = applicationContext;
    }

    public static Set<ApiInfo> initHandlerMethods(String beanName) {
        Set<ApiInfo> mappings = new HashSet<>();
        RequestMappingHandlerMapping handlerMapping = getBean(RequestMappingHandlerMapping.class);
        Class beanType = ApplicationContextProvider.getType(beanName);
        //判断是否为 handler
        if (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)) {
            //属于handler
            final Class<?> userType = ClassUtils.getUserClass(beanType);
            try {
                Method getMapping = RequestMappingHandlerMapping.class.getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
                getMapping.setAccessible(true);

                Method[] methods = userType.getMethods();
                for (Method method : methods) {
                    if (AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class)) {
                        Object mapping = getMapping.invoke(handlerMapping, method, userType);
                        try {
                            handlerMapping.registerMapping((RequestMappingInfo) mapping, beanName, method);
                            mappings.add(new ApiInfo((RequestMappingInfo) mapping));
                        } catch (IllegalStateException ex) {
                            log.error("register mapping error:" + ex.getMessage());
                        }
                    }
                }
            } catch (NoSuchMethodException e) {
                log.error(e.getMessage());
            } catch (IllegalAccessException e) {
                log.error(e.getMessage());
            } catch (InvocationTargetException e) {
                log.error(e.getMessage());
            }
        }
        return mappings;
    }

    public static void removeHandlerMappings(String beanName) {
        //判断是否是handlers
        Class beanType = ApplicationContextProvider.getType(beanName);
        if (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)) {
            //属于handler
            unregisterMapping(beanType);
        }
    }

    public static void unregisterMapping(Class handler) {
        RequestMappingHandlerMapping handlerMapping = getBean(RequestMappingHandlerMapping.class);
        final Class<?> userType = ClassUtils.getUserClass(handler);
        try {
            Method getMapping = RequestMappingHandlerMapping.class.getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
            getMapping.setAccessible(true);

            Method[] methods = userType.getMethods();
            for (Method method : methods) {
                if (AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class)) {
                    Object mapping = getMapping.invoke(handlerMapping, method, userType);
                    handlerMapping.unregisterMapping((RequestMappingInfo) mapping);
                }
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        try {
            return getApplicationContext().getBean(clazz);
        } catch (Exception ex) {
            ((DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory()).clearMetadataCache();
            throw new RuntimeException("获取serviceImpl失败");

        }
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    public static void removeBean(String beanName) {
        BeanDefinitionRegistry beanDefReg = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        beanDefReg.getBeanDefinition(beanName);
        if (beanDefReg.isBeanNameInUse(beanName)) {
            beanDefReg.removeBeanDefinition(beanName);
        }
    }

    public static Class getType(String name) {
        return getApplicationContext().getType(name);
    }


    public static void addBean(String name, Class clazz) {
        BeanDefinitionRegistry beanDefReg = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        if (!beanDefReg.isBeanNameInUse(name)) {
            beanDefReg.registerBeanDefinition(name, beanDefinitionBuilder.getBeanDefinition());
        }
    }

    public static List<String> scanMapperBean(ClassLoader cl, String... mapperScanPackages) {
        BeanDefinitionRegistry registry = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        List<String> oldBeans = Arrays.asList(registry.getBeanDefinitionNames());
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setMapperFactoryBean(new SelfMapperFactoryBean());
        PathMatchingResourcePatternResolver resolver = new SelfPathMatchingResolver(cl);
        scanner.setResourceLoader(resolver);
        scanner.registerFilters();
        scanner.doScan(mapperScanPackages);
        String[] newBeans = registry.getBeanDefinitionNames();
        List<String> registered = new ArrayList<>();
        for (String bean : newBeans) {
            if (!oldBeans.contains(bean)) {
                registered.add(bean);
            }
        }
        return registered;
    }

    public static List<String> scanBean(ClassLoader cl, String... scanPackages) {
        BeanDefinitionRegistry registry = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        List<String> oldBeans = Arrays.asList(registry.getBeanDefinitionNames());
        SelfClassPathScanner scanner = new SelfClassPathScanner(registry);
        PathMatchingResourcePatternResolver resolver = new SelfPathMatchingResolver(cl);
        MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(resolver);
        scanner.setResourceLoader(resolver);
        scanner.setMetadataReaderFactory(metadataReaderFactory);
        scanner.setClassLoader(cl);
        scanner.scan(scanPackages);
        String[] newBeans = registry.getBeanDefinitionNames();
        List<String> registered = new ArrayList<>();
        for (String bean : newBeans) {
            if (!oldBeans.contains(bean)) {
                registered.add(bean);
            }
        }
        return registered;
    }


    public static void printAllBeans() {
        String[] beans = applicationContext.getBeanDefinitionNames();
        for (String beanName : beans) {
            System.out.println("BeanName:" + beanName);
        }
    }
}